#pragma

#include <iostream>
#include <sys/select.h> //select() 函数和相关的数据结构,实现I/O多路复用的头文件
#include <sys/time.h>   //定义了 struct timeval 结构体，用于表示时间间隔，通常与 select() 函数配合使用来指定超时时间
#include "Socket.hpp"

using namespace std;

static const uint16_t defaultport = 9090;
static const int fd_num_max = (sizeof(fd_set) * 8); // 文件描述符数据的最大大小
int defaultfd = -1;                                 // 默认fd为-1，该文件描述符未被使用

//select多路复用通过三个数组与对应的读、写、异常位图对所有文件描述符进行管理
//且位图为输入输出型参数，不保留状态信息，每次对位图重新设置
class SelectServer
{
public:
    SelectServer(uint16_t port = defaultport) : _port(port)
    {
        for (int i = 0; i < fd_num_max; i++)
        {
            rfd_array[i] = defaultfd; // 将读文件描述符集合数组初始化
        }
    }

    bool Init()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
        return true;
    }

    void Accepter()
    {
        // select等待结束，连接事件就绪，
        std::string clientip;
        uint16_t clientport = 0;
        int sock = _listensock.Accept(&clientip, &clientport);
        if (sock < 0)
            return;
        lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);
        // sock -> fd_array[]
        int pos = 1;
        for (; pos < fd_num_max; pos++) // 第二个循环:将建立好的连接（套接字）放入rfd_array数组，下一次监听这些套接字
        {
            if (rfd_array[pos] != defaultfd)
                continue;
            else
                break;
        }
        if (pos == fd_num_max)
        {
            lg(Warning, "server is full, close %d now!", sock);
            close(sock);
        }
        else
        { // 寻找未被使用的数组位置，填入套接字
            rfd_array[pos] = sock;
            PrintFd();
        }
    }

    void Recver(int fd, int pos)
    {
        // demo
        char buffer[1024];
        ssize_t n = read(fd, buffer, sizeof(buffer) - 1); 
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "get a messge: " << buffer << endl;
        }
        else if (n == 0)
        {
            lg(Info, "client quit, me too, close fd is : %d", fd);
            close(fd);
            rfd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
        else
        {
            lg(Warning, "recv error: fd is : %d", fd);
            close(fd);
            rfd_array[pos] = defaultfd; // 这里本质是从select中移除
        }
    }

    void Dispatcher(fd_set &rfds) // 分析返回的读文件描述符集合
    {
        for (int i = 0; i < fd_num_max; i++) // 这是第三个循环
        {
            int fd = rfd_array[i];
            if (fd == defaultfd)
            {
                continue;
            }
            if (FD_ISSET(fd, &rfds))
            {
                if (fd == _listensock.Fd()) // 如果监听套接字返回后被设置
                {
                    Accepter(); // 处理连接
                }
                else
                {
                    Recver(fd, i); // 处理客户端请求
                }
            }
        }
    }

    void Start()
    {
        int listensock = _listensock.Fd(); // 从套接字类中提取套接字的函数
        rfd_array[0] = listensock;         // 服务器的第一个套接字永远是监听套接字
        for (;;)
        {
            fd_set rfds; // 读文件描述符集合位图，输入输出型参数
            FD_ZERO(&rfds);
            int maxfd = rfd_array[0];
            for (int i = 0; i < fd_num_max; i++) // 根据rfd数组设置位图
            {
                if (rfd_array[i] == defaultfd)
                {
                    continue;
                }
                FD_SET(rfd_array[i], &rfds); // 将有效的文件描述符添加到 rfds 集合中,对应比特位置1
                if (maxfd < rfd_array[i])
                {
                    maxfd = rfd_array[i];
                    lg(Info, "max fd update, max fd is: %d", maxfd);
                }
                // accept?不能直接accept！检测并获取listensock上面的事件，新连接到来，等价于读事件就绪
                struct timeval timeout = {2, 0}; // 输入输出，可能要进行周期的重复设置,下面是结构体成员变量
                                                 // tv_sec：这是一个 time_t 类型的成员，用于表示秒数;tv_usec：这是一个 suseconds_t 类型的成员，用于表示微秒数
                                                 // struct timeval timeout = {0, 0};非阻塞等待IO
                                                 //  如果事件就绪，上层不处理，select会一直通知你
                                                 //  select告诉你就绪了，接下来的一次读取，我们读取fd的时候，不会被阻塞
                                                 //  rfds: 输入输出型参数。 1111 1111 -> 0000 0000
                int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);
                switch (n)
                {
                case 0:
                    cout << "time out,timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;
                    break;
                case -1:
                    cerr << "select error" << endl;
                    break;
                default:
                    cout << "get a new link!!!!!" << endl;
                    Dispatcher(rfds); // 就绪的事件和fd进行分离，因为就绪的事件不止一个
                    break;
                }
            }
        }
    }

    void PrintFd() // 打印被设置的文件描述符
    {
        cout << "online fd list: ";
        for (int i = 0; i < fd_num_max; i++)
        {
            if (rfd_array[i] == defaultfd)
                continue;
            cout << rfd_array[i] << " ";
        }
        cout << endl;
    }

private:
    Sock _listensock; // 监听建立连接的套接字
    uint16_t _port;
    int rfd_array[fd_num_max]; // 数组, 用户维护的！
    // int wfd_array[fd_num_max];
};
